package container

import (
	"fmt"
	"github.com/Sirupsen/logrus"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"
)

func RunContainerInitProcess(command string, args []string) error {
	logrus.Infof("command %s", command)
	cmdSlc := readUserCommand()
	if cmdSlc == nil || len(cmdSlc) == 0 {
		return fmt.Errorf("Run container get user command error, cmdSlc is nil")
	}

	// 设置rootfs和proc的挂载
	setUpMount()

	// 获取目标程序的绝对路径,在环境变量中查找可执行二进制文件
	path, err := exec.LookPath(cmdSlc[0])
	if err != nil {
		logrus.Errorf("init => Exec loop path error %v", err)
		return err
	}
	logrus.Infof("init => Find path %s", path)
	// syscall.Exec 调用了Kernel的init execve系统函数, 作用: 执行对应程序时会覆盖当前进程的镜像、数据和堆栈等信息包括pid
	// 将用户指定的进程运行起来,把最初的init进程给替换掉.
	if err := syscall.Exec(path, cmdSlc[0:], os.Environ()); err != nil {
		logrus.Errorf("init => %v", err)
	}
	return nil
}

// 从管道中读取命令, 读管道中没有数据,那么会阻塞直到管道中有数据.
func readUserCommand() []string {
	// index为3的文件描述符, 也就是传递进来的读管道
	pipe := os.NewFile(uintptr(3), "pipe")
	msg, err := ioutil.ReadAll(pipe)
	if err != nil {
		logrus.Errorf("init read pipe error %v", err)
		return nil
	}
	msgStr := string(msg)
	return strings.Split(msgStr, " ")
}

// init 挂载
func setUpMount() {
	// 获取当前文件路径
	pwd, err := os.Getwd()
	if err != nil {
		logrus.Errorf("init => Get current location error %v", err)
		return
	}

	logrus.Infof("init => cmd 当前路径 %s", pwd)

	// 设置rootfs挂载
	pivotRoot(pwd)

	// proc挂载
	// MS_NOEXEC 在本文件系统中不允许运行其他程序
	// MS_NOSUID 在本文件系中运行程序的时候, 不允许set-user-ID或set-group-ID
	// MS_NODEV 所有mount的系统默认设定的参数
	defaultMountFlags := syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
	syscall.Mount("proc", "/proc", "proc", uintptr(defaultMountFlags), "")

	syscall.Mount("tmpfs", "/dev", "tmpfs", syscall.MS_NOSUID|syscall.MS_STRICTATIME, "mode=755")

}

// rootfs挂载
func pivotRoot(newRootPath string) error {

	// 设置父root为私有(不共享)
	syscall.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, "")

	// 为了使当前root的老 root 和新 root 不在同一个文件系统下，我们把root重新mount了一次
	// bind mount是把相同的内容换了一个挂载点的挂载方法
	if err := syscall.Mount(newRootPath, newRootPath, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil {
		return fmt.Errorf("init => Mount rootfs to it self error : %v", err)
	}
	// 创建 rootfs/.pivot_root 存储 old_root
	oldPivotDir := filepath.Join(newRootPath, ".pivot_root")
	if err := os.Mkdir(oldPivotDir, 0777); err != nil {
		return err
	}
	// pivot_root 到新的rootfs, 现在老的old_root 是挂在在rootfs/.pivot_root
	// 挂在点现在依然可以在mount命令中看到
	// /root/busybox为新的rootfs
	if err := syscall.PivotRoot(newRootPath, oldPivotDir); err != nil {
		return fmt.Errorf("init => pivot_root %v", err)
	}
	// 修改当前的工作目录到根目录
	if err := syscall.Chdir("/"); err != nil {
		return fmt.Errorf("init => chdir / %v", err)
	}

	oldPivotDir = filepath.Join("/", ".pivot_root")
	// unmount rootfs/.pivot_root
	if err := syscall.Unmount(oldPivotDir, syscall.MNT_DETACH); err != nil {
		return fmt.Errorf("init => unmount pivot_root dir %v", err)
	}
	fmt.Printf("%s rootfs挂载成功\n", newRootPath)
	// 删除临时文件夹
	return os.Remove(oldPivotDir)
}
